package com.microservice.skeleton.upms.constraints;

/**
 * @author: lijin
 * @date: 2018年11月07日
 */

import org.hibernate.validator.constraints.*;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringUtils;

import javax.validation.constraints.*;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * A {@link ConstraintDescriptionResolver} that resolves constraint descriptions from a
 * {@link ResourceBundle}. The resource bundle's keys are the name of the constraint with
 * {@code .description} appended. For example, the key for the constraint named
 * {@code javax.validation.constraints.NotNull} is
 * {@code javax.validation.constraints.NotNull.description}.
 * <p>
 * Default descriptions are provided for Bean Validation 2.0's constraints:
 *
 * <ul>
 * <li>{@link AssertFalse}
 * <li>{@link AssertTrue}
 * <li>{@link DecimalMax}
 * <li>{@link DecimalMin}
 * <li>{@link Digits}
 * <li>{@link Email}
 * <li>{@link Future}
 * <li>{@link FutureOrPresent}
 * <li>{@link Max}
 * <li>{@link Min}
 * <li>{@link Negative}
 * <li>{@link NegativeOrZero}
 * <li>{@link NotBlank}
 * <li>{@link NotEmpty}
 * <li>{@link NotNull}
 * <li>{@link Null}
 * <li>{@link Past}
 * <li>{@link PastOrPresent}
 * <li>{@link Pattern}
 * <li>{@link Positive}
 * <li>{@link PositiveOrZero}
 * <li>{@link Size}
 * </ul>
 *
 * <p>
 * Default descriptions are also provided for Hibernate Validator's constraints:
 *
 * <ul>
 * <li>{@link CodePointLength}
 * <li>{@link CreditCardNumber}
 * <li>{@link Currency}
 * <li>{@link EAN}
 * <li>{@link org.hibernate.validator.constraints.Email}
 * <li>{@link Length}
 * <li>{@link LuhnCheck}
 * <li>{@link Mod10Check}
 * <li>{@link Mod11Check}
 * <li>{@link org.hibernate.validator.constraints.NotBlank}
 * <li>{@link org.hibernate.validator.constraints.NotEmpty}
 * <li>{@link Range}
 * <li>{@link SafeHtml}
 * <li>{@link URL}
 * </ul>
 *
 * @author Andy Wilkinson
 */
public class ResourceBundleConstraintDescriptionResolver
        implements ConstraintDescriptionResolver {

    private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper(
            "${", "}");

    private final ResourceBundle defaultDescriptions;

    private final ResourceBundle userDescriptions;

    /**
     * Creates a new {@code ResourceBundleConstraintDescriptionResolver} that will resolve
     * descriptions by looking them up in a resource bundle with the base name
     * {@code org.springframework.restdocs.constraints.ConstraintDescriptions} in the
     * default locale loaded using the thread context class loader.
     */
    public ResourceBundleConstraintDescriptionResolver() {
        this(getBundle("ConstraintDescriptions"));
    }

    /**
     * Creates a new {@code ResourceBundleConstraintDescriptionResolver} that will resolve
     * descriptions by looking them up in the given {@code resourceBundle}.
     *
     * @param resourceBundle the resource bundle
     */
    public ResourceBundleConstraintDescriptionResolver(ResourceBundle resourceBundle) {
        this.defaultDescriptions = getBundle("DefaultConstraintDescriptions");
        this.userDescriptions = resourceBundle;
    }

    private static ResourceBundle getBundle(String name) {
        try {
            return ResourceBundle.getBundle(
                    ResourceBundleConstraintDescriptionResolver.class.getPackage()
                            .getName() + "." + name,
                    Locale.getDefault(), Thread.currentThread().getContextClassLoader());
        }
        catch (MissingResourceException ex) {
            return null;
        }
    }

    @Override
    public String resolveDescription(Constraint constraint) {
        String key = constraint.getName() + ".message";
        return this.propertyPlaceholderHelper.replacePlaceholders(getDescription(key),
                new ConstraintPlaceholderResolver(constraint));
    }

    private String getDescription(String key) {
        try {
            if (this.userDescriptions != null) {
                return this.userDescriptions.getString(key);
            }
        }
        catch (MissingResourceException ex) {
            // Continue and return default description, if available
        }
        return this.defaultDescriptions.getString(key);
    }

    private static final class ConstraintPlaceholderResolver
            implements PropertyPlaceholderHelper.PlaceholderResolver {

        private final Constraint constraint;

        private ConstraintPlaceholderResolver(Constraint constraint) {
            this.constraint = constraint;
        }

        @Override
        public String resolvePlaceholder(String placeholderName) {
            Object replacement = this.constraint.getConfiguration().get(placeholderName);
            if (replacement == null) {
                return null;
            }
            if (replacement.getClass().isArray()) {
                return StringUtils.arrayToDelimitedString((Object[]) replacement, ", ");
            }
            return replacement.toString();
        }

    }

}
